#include "content.h"
#include "view.h"
#include "system.h"
#include "gesture.h"
#include "log.h"
#include "timer.h"

#include <math.h>
#include <string.h>
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>

typedef struct _CONTENT
{
    ufoSize size;
	ufoRect  touch_rect;
    int pagingEnabled;
	int alwaysBounceVertical;
	int alwaysBounceHorizontal;
    int pageIndex_x, pageIndex_y;
    
    UFO_Animation scroll_animation;
    //UFO_Animation animation_wheel;
	
	UFO_Timer autoscroll_timer;
	ufoPoint autoscroll_speed;
	ufoPoint autoscroll_accelerate;
	long autoscroll_timestamp;
	UFO_Direction autoscroll_dir;
}CONTENT;

#define CONTENT_SPEED 500.0f
#define CONTENT_ACCE (CONTENT_SPEED*0.998f)
#define CONTENT_ACCE_REDUCE (CONTENT_SPEED*0.998f*75.0f)

#define CONTENT_DRAG_LIMIT 170.0f

int ufoContentSetPagingEnabled(UFO_View view, int enabled)
{
    CONTENT *pContent = ufoViewGetUserData(view);
    if (pContent) {
		if (enabled != pContent->pagingEnabled) {
			pContent->pagingEnabled = enabled;
			pContent->pageIndex_x = -1;
			pContent->pageIndex_y = -1;
		}
        
		return 1;
    }
    return 0;
}

int ufoContentSetAlwaysBounceVertical(UFO_View view, int enabled)
{
	CONTENT *pContent = ufoViewGetUserData(view);
	if (pContent) {
		if (enabled != pContent->alwaysBounceVertical) {
			pContent->alwaysBounceVertical = enabled;
		}
        
		return 1;
	}
	return 0;
}

int ufoContentSetSize(UFO_View view, float width, float height)
{
    CONTENT *pContent = ufoViewGetUserData(view);
    if (pContent) {
        ufoRect rect = {0, 0, width, height};
        pContent->size.width = width;
        pContent->size.height = height;
        ufoViewSetRect(view, &rect);
    }
    
    return 0;
}

int ufoContentScrollTo(UFO_View view, float x, float y, int animated)
{
    CONTENT *pContent = ufoViewGetUserData(view);
	if (pContent) {
		UFO_View parent = ufoViewGetParent(view);
		ufoRect rcParent = {0};
		ufoRect rcContent = {0};
		ufoViewGetRect(parent, &rcParent);
		ufoViewGetRect(view, &rcContent);
		pContent->pageIndex_x = (-rcContent.left)/(rcParent.right-rcParent.left);
		pContent->pageIndex_y = (-rcContent.top)/(rcParent.bottom-rcParent.top);
        
		if (!animated) {
			ufoRect rect = {0};
			ufoViewGetRect(view, &rect);
			ufoRectMoveTo(&rect, x, y);
			ufoViewSetRect(view, &rect);
			//ufoLog("ufoContentScrollTo", "ufoContentScrollTo rect = %d %d %d %d", rect.left, rect.top, rect.right, rect.bottom);
		}
		else {
			UFO_Animation_Value value = {Animation_rect};
			ufoViewGetRect(view, &value.rect);
			ufoRectMoveTo(&value.rect, x, y);
            
			ufoAnimationStop(pContent->scroll_animation);
			ufoAnimationSetDuration(pContent->scroll_animation, 250);
			ufoAnimationSetTimingFunction(pContent->scroll_animation, TimingFunctionEaseInEaseOut);
			ufoAnimationSetToValue(pContent->scroll_animation, value);
			ufoAnimationPlay(pContent->scroll_animation);
            
            //ufoLog("ufoContentScrollTo", "ufoContentScrollTo rect = %d %d %d %d", value.rect.left, value.rect.top, value.rect.right, value.rect.bottom);
		}
	}
    return 1;
}

int internal_content_adjust(UFO_View view, int dir)
{
	CONTENT *pContent = ufoViewGetUserData(view);
	UFO_Animation_Value value = {Animation_rect};
	ufoRect rc = {0};
	ufoRect rcParent = {0};
	float dx = 0, dy = 0;
	UFO_View parent = ufoViewGetParent(view);
    
	ufoViewGetRect(view, &rc);
    rc.bottom = rc.top+pContent->size.height;//### fix animation deviation
	ufoViewGetRect(parent, &rcParent);
    
	ufoAnimationStop(pContent->scroll_animation);
	ufoViewGetRect(view, &value.rect);
	
	if (rc.top >= 0) {
		dy = -rc.top;
	}
	else if (rc.bottom <= rcParent.bottom-rcParent.top) {
		if (rc.bottom-rc.top >= rcParent.bottom-rcParent.top)
			dy = (rcParent.bottom-rcParent.top) - rc.bottom;
		else
			dy = (rc.bottom-rc.top) - rc.bottom;
	}
    
	if (rc.left >= 0) {
		dx = -rc.left;
	}
	else if (rc.right <= rcParent.right-rcParent.left) {
		if (rc.right-rc.left >= rcParent.right-rcParent.left)
			dx = (rcParent.right-rcParent.left) - rc.right;
		else
			dx = (rc.right-rc.left) - rc.right;
	}
    
    if (pContent->pagingEnabled && (fabs(dy) <= 1e-8 || fabs(dx) <= 1e-8) ) {
		float width = rcParent.right-rcParent.left;
		float height = rcParent.bottom-rcParent.top;
        
		if (pContent->size.height > height) {
			int pages = pContent->size.height / height + !!((int)(pContent->size.height)%(int)height);
			float dt = 0;
			if (pContent->pageIndex_y == -1)
				pContent->pageIndex_y = (-rc.top) / height;
			dt = -rc.top - pContent->pageIndex_y*height;
			value.rect = rc;
            
			if (dir & UFO_TOP) {
				pContent->pageIndex_y++;
			}
			else if (dir & UFO_BOTTOM) {
				pContent->pageIndex_y--;
			}
			else if (dt > height/2) {
				pContent->pageIndex_y--;
			}
            
			if (pContent->pageIndex_y >= pages)
				pContent->pageIndex_y = pages-1;
			if (pContent->pageIndex_y < 0)
				pContent->pageIndex_y = 0;
            
			value.rect.top = -pContent->pageIndex_y*height;
			value.rect.bottom = -pContent->pageIndex_y*height+rc.bottom-rc.top;
            
			if (value.rect.bottom < rcParent.bottom-rcParent.top) {
				value.rect.bottom = rcParent.bottom-rcParent.top;
				value.rect.top = value.rect.bottom-pContent->size.height;
			}
		}
        
		if (pContent->size.width > width) {
			int pages = pContent->size.width / width + !!((int)(pContent->size.width)%(int)width);
			float dt = 0;
			if (pContent->pageIndex_x == -1)
				pContent->pageIndex_x = (-rc.left) / width;
			dt = -rc.left - pContent->pageIndex_x*width;
            
			if (dir & UFO_LEFT) {
				pContent->pageIndex_x++;
			}
			else if (dir & UFO_RIGHT) {
				pContent->pageIndex_x--;
			}
			else if (fabs(dt) > width/2) {
                if (dt > 0)
                    pContent->pageIndex_x++;
                else
                    pContent->pageIndex_x--;
			}
            
			if (pContent->pageIndex_x >= pages)
				pContent->pageIndex_x = pages-1;
			if (pContent->pageIndex_x < 0)
				pContent->pageIndex_x = 0;
            
			value.rect.left = -pContent->pageIndex_x*width;
			value.rect.right = -pContent->pageIndex_x*width+rc.right-rc.left;
            
			if (value.rect.right < rcParent.right-rcParent.left) {
				value.rect.right = rcParent.right-rcParent.left;
				value.rect.left = value.rect.right-pContent->size.width;
			}
		}
        
        if (!ufoRectEqual(&rc, &value.rect)) {
            ufoAnimationSetToValue(pContent->scroll_animation, value);
            ufoAnimationSetDuration(pContent->scroll_animation, 270);
            ufoAnimationSetTimingFunction(pContent->scroll_animation, TimingFunctionQuadraticEaseOut);
            ufoAnimationPlay(pContent->scroll_animation);
        }
    }
	else {
		ufoRectOffset(&value.rect, dx, dy);
        
        if (!ufoRectEqual(&rc, &value.rect)) {
            ufoAnimationSetToValue(pContent->scroll_animation, value);
            ufoAnimationSetDuration(pContent->scroll_animation, 550);
            ufoAnimationSetTimingFunction(pContent->scroll_animation, TimingFunctionQuinticEaseOut);
            ufoAnimationPlay(pContent->scroll_animation);
        }
	}
    
	return 1;
}

int default_content_proc(UFO_View view, int id, void* wParam, void* lParam)
{
	CONTENT *pContent = ufoViewGetUserData(view);
	if (!pContent) {
		pContent = malloc(sizeof(CONTENT));
		memset(pContent, 0, sizeof(CONTENT));
		ufoViewSetUserData(view, pContent);
	}
    
	switch (id)
	{
        case MSG_SIZE_CHANGE:
		{
			UFO_NOTIFY_STRUCT notify = {0};
			notify.id = MSG_NOTIFY_CONTENT_CHANGE;
			ufoViewSendMessage(ufoViewGetParent(view), MSG_NOTIFY, view, &notify);//### notify changed
		}break;
            
        case MSG_TOUCH_MOVE:
		{
			((UFO_TOUCH_STRUCT*)lParam)->bHandled = 1;//### not pass to parent view
		}break;
            
        case MSG_TOUCH_DOWN:
        {
            ufoViewGetRect(view, &pContent->touch_rect);
            ufoAnimationStop(pContent->scroll_animation);
			if (pContent->autoscroll_timer) {
				ufoTimerDelete(pContent->autoscroll_timer);
				pContent->autoscroll_timer = 0;
			}
			//((UFO_TOUCH_STRUCT*)lParam)->bHandled = 1;//### not pass to parent view
        }break;
            
        case MSG_TOUCH_UP:
        {
			internal_content_adjust(view, 0);//### if over bound, drag back by animation
            
			//((UFO_TOUCH_STRUCT*)lParam)->bHandled = 1;//### not pass to parent view
        }break;
            
        case MSG_GESTURE_SWIPE:
        {
            UFO_GESTURE_SWIPE_STRUCT *swipe = (UFO_GESTURE_SWIPE_STRUCT*)wParam;
            pContent->autoscroll_dir = ufoGestureCalDircetion(swipe->point, swipe->numberOfPoint);
            
			ufoAnimationStop(pContent->scroll_animation);
            
			if (pContent->pagingEnabled) {//### pagingEnabled will not support autoScroll
				internal_content_adjust(view, pContent->autoscroll_dir);
			}
			else {
                ufoPoint speed = ufoGestureCalcSpeed(swipe->point, swipe->numberOfPoint, swipe->deltaTime);
				ufoRect rect = {0};
				ufoRect rcParent = {0};
				ufoViewGetRect(view, &rect);
				ufoViewGetRect(ufoViewGetParent(view), &rcParent);
                
				if (pContent->autoscroll_timer) {
					ufoTimerDelete(pContent->autoscroll_timer);
					pContent->autoscroll_timer = 0;
				}
                
				ufoLog("speed", "speed x=%f y=%f", speed.x, speed.y);
				if (speed.x >= 12.0f)
					speed.x = 12.0f;
                
				if (speed.y >= 12.0f)
					speed.y = 12.0f;
                
                //### auto scroll begin
                pContent->autoscroll_timestamp = ufoGetCurTime();
                pContent->autoscroll_timer = ufoTimerCreate(view, 0, -1, 0, 0);
                
				if (rect.right-rect.left > rcParent.right-rcParent.left) {
                    
					if (pContent->autoscroll_dir & UFO_LEFT) {

						pContent->autoscroll_speed.x = -CONTENT_SPEED*speed.x;
						pContent->autoscroll_accelerate.x = CONTENT_ACCE;
					}
					else if (pContent->autoscroll_dir & UFO_RIGHT) {
						pContent->autoscroll_speed.x = CONTENT_SPEED*speed.x;
						pContent->autoscroll_accelerate.x = -CONTENT_ACCE;
					}
				}
				else if (rect.bottom-rect.top > rcParent.bottom-rcParent.top) {
					
					if (pContent->autoscroll_dir & UFO_TOP) {
						pContent->autoscroll_speed.y = -CONTENT_SPEED*speed.y;
						pContent->autoscroll_accelerate.y = CONTENT_ACCE;
					}
					else if (pContent->autoscroll_dir & UFO_BOTTOM) {
						pContent->autoscroll_speed.y = CONTENT_SPEED*speed.y;
						pContent->autoscroll_accelerate.y = -CONTENT_ACCE;
					}
				}
                else {//### not need scroll
                    ufoTimerDelete(pContent->autoscroll_timer);
                    pContent->autoscroll_timer = 0;
                    internal_content_adjust(view, 0);
                }
			}
		}break;
            
        case MSG_TIMER:
		{
			if (wParam == pContent->autoscroll_timer) {
				//ufoAnimationGetState(pContent->autoscroll_timer )
				long curTime = ufoGetCurTime();
				float delta = (curTime-pContent->autoscroll_timestamp)/1000.0f;
				ufoRect rect = {0};
				ufoRect rcParent = {0};
				ufoViewGetRect(view, &rect);
				ufoViewGetRect(ufoViewGetParent(view), &rcParent);
                
                if ((rect.right-rect.left) > (rcParent.right-rcParent.left)) {
                    if (pContent->autoscroll_dir & UFO_LEFT) {
                        
                        if (rect.right <= rcParent.right-rcParent.left) {
                            pContent->autoscroll_accelerate.x = CONTENT_ACCE_REDUCE;
                        }
                        
                        pContent->autoscroll_speed.x += pContent->autoscroll_accelerate.x*delta;
                        
                        if (pContent->autoscroll_speed.x < 0)
                            rect.left += pContent->autoscroll_speed.x*delta;
                        else if (pContent->autoscroll_timer){
                            ufoTimerDelete(pContent->autoscroll_timer);
                            pContent->autoscroll_timer = 0;
                            internal_content_adjust(view, 0);
                        }
                    }
                    else if (pContent->autoscroll_dir & UFO_RIGHT) {
                        
                        if (rect.left >= 0) {
                            pContent->autoscroll_accelerate.x = -CONTENT_ACCE_REDUCE;
                        }
                        
                        pContent->autoscroll_speed.x += pContent->autoscroll_accelerate.x*delta;
                        
                        if (pContent->autoscroll_speed.x > 0)
                            rect.left += pContent->autoscroll_speed.x*delta;
                        else if (pContent->autoscroll_timer){
                            ufoTimerDelete(pContent->autoscroll_timer);
                            pContent->autoscroll_timer = 0;
                            internal_content_adjust(view, 0);
                        }
                    }
                }
                
				if ((rect.bottom-rect.top) > (rcParent.bottom-rcParent.top)) {
                    if (pContent->autoscroll_dir & UFO_TOP) {
                        
                        if (rect.bottom <= rcParent.bottom-rcParent.top) {
                            pContent->autoscroll_accelerate.y = CONTENT_ACCE_REDUCE;
                        }
                        
                        pContent->autoscroll_speed.y += pContent->autoscroll_accelerate.y*delta;
                        if (pContent->autoscroll_speed.y < 0)
                            rect.top += pContent->autoscroll_speed.y*delta;
                        else if (pContent->autoscroll_timer){
                            ufoTimerDelete(pContent->autoscroll_timer);
                            pContent->autoscroll_timer = 0;
                            internal_content_adjust(view, 0);
                        }
                    }
                    else if (pContent->autoscroll_dir & UFO_BOTTOM) {
                        
                        if (rect.top >= 0) {
                            pContent->autoscroll_accelerate.y = -CONTENT_ACCE_REDUCE;
                        }
                        
                        pContent->autoscroll_speed.y += pContent->autoscroll_accelerate.y*delta;
                        
                        if (pContent->autoscroll_speed.y > 0)
                            rect.top += pContent->autoscroll_speed.y*delta;
                        else if (pContent->autoscroll_timer){
                            ufoTimerDelete(pContent->autoscroll_timer);
                            pContent->autoscroll_timer = 0;
                            internal_content_adjust(view, 0);
                        }
                    }
                }
                
                if (!pContent->autoscroll_timer) {
                    UFO_View parent = ufoViewGetParent(view);
                    UFO_NOTIFY_STRUCT notify = {0};
                    notify.id = MSG_NOTIFY_CONTENT_ADJUST_FINISH;
                    ufoViewSendMessage(parent, MSG_NOTIFY, view, &notify);
                }
                
				ufoContentScrollTo(view, rect.left, rect.top, 0);
				pContent->autoscroll_timestamp = curTime;
			}
		}
            break;
            
        case MSG_GESTURE_PAN:
        {
			ufoPoint *delta = (ufoPoint*)wParam;
            ufoRect rc = {0};
            ufoRect rcParent = {0};
            float dx = 0.0f, dy = 0.0f;
			ufoViewGetRect(view, &rc);
            ufoViewGetRect(ufoViewGetParent(view), &rcParent);
            
            if (rc.top >= 0) {
				if (rc.bottom-rc.top > rcParent.bottom-rcParent.top
					|| pContent->alwaysBounceVertical) {//### child must be small than parent or be alwaysBounce
                    
					if (pContent->touch_rect.top >= 0)
						dy = pContent->touch_rect.top + (delta->y)/((1.0f+(delta->y)/CONTENT_DRAG_LIMIT));
					else {
						float delta_y = delta->y+pContent->touch_rect.top;
						dy = 0.0f + (delta_y)/((1.0f+(delta_y)/CONTENT_DRAG_LIMIT));
					}
				}
            }
			else if (rc.bottom <= rcParent.bottom - rcParent.top) {
				if (pContent->touch_rect.bottom <= rcParent.bottom - rcParent.top)
					dy = pContent->touch_rect.top + (delta->y)/((1.0f-(delta->y)/CONTENT_DRAG_LIMIT));
				else {
					float delta_y = delta->y-(rcParent.bottom - rcParent.top - pContent->touch_rect.bottom);
					dy = pContent->touch_rect.top+(rcParent.bottom - rcParent.top - pContent->touch_rect.bottom)
                    + (delta_y)/((1.0f-(delta_y)/CONTENT_DRAG_LIMIT));
				}
            }
			
            if (rc.left >= 0) {
				if (rc.right-rc.left > rcParent.right-rcParent.left) {
					if (pContent->touch_rect.left >= 0)
						dx = pContent->touch_rect.left + (delta->x)/((1.0f+(delta->x)/CONTENT_DRAG_LIMIT));
					else {
						float delta_x = delta->x+pContent->touch_rect.left;
						dx = 0.0f + (delta_x)/((1.0f+(delta_x)/CONTENT_DRAG_LIMIT));
					}
				}
			}
			else if (rc.right <= rcParent.right - rcParent.left) {
				if (pContent->touch_rect.right <= rcParent.right - rcParent.left)
					dx = pContent->touch_rect.left + (delta->x)/((1.0f-(delta->x)/CONTENT_DRAG_LIMIT));
				else {
					float delta_x = delta->x-(rcParent.right - rcParent.left - pContent->touch_rect.right);
					dx = pContent->touch_rect.left+(rcParent.right - rcParent.left - pContent->touch_rect.right)
                    + (delta_x)/((1.0f-(delta_x)/CONTENT_DRAG_LIMIT));
				}
			}
            
			ufoRectMoveTo(&rc,
                          fabs(dx) <= 1e-3 ? pContent->touch_rect.left + (delta->x) : dx,
                          fabs(dy) <= 1e-3 ? pContent->touch_rect.top + (delta->y) : dy
                          );
            
			if (rc.right-rc.left <= rcParent.right-rcParent.left)
				ufoRectOffset(&rc, -rc.left, 0);
            
			if (rc.bottom-rc.top <= rcParent.bottom-rcParent.top && !pContent->alwaysBounceVertical)
				ufoRectOffset(&rc, 0, -rc.top);
            
			ufoViewSetRect(view, &rc);
        }break;
            
        case MSG_TOUCH_WHEEL:
		{
			ufoRect rect = {0};
			int delta = (int)lParam;
			ufoViewGetRect(view, &rect);
			ufoContentScrollTo(view, rect.left, rect.top+200*delta, 1);
			
			((UFO_TOUCH_STRUCT*)lParam)->bHandled = 1;//### not pass to parent view
		}break;
            
       case MSG_ANIMATION_FINISH:
		{
			if (pContent->scroll_animation == (UFO_Animation)wParam) {
                UFO_View parent = ufoViewGetParent(view);
                UFO_NOTIFY_STRUCT notify = {0};
                notify.id = MSG_NOTIFY_CONTENT_ADJUST_FINISH;
                ufoViewSendMessage(parent, MSG_NOTIFY, view, &notify);
                
				internal_content_adjust(view, 0);
			}
			break;
		}
            
       case MSG_CREATE:
		{
            ufoViewSetGesture(view, GESTURE_SWIPE|GESTURE_PAN);
            pContent->scroll_animation = ufoAnimationCreate(view);
		}break;
	}
    
	return ufoViewDefaultProc(view, id, wParam, lParam);//### pass to default view proc
}